/*
    Description : PD80 03 Levels for Reshade https://reshade.me/
    Author      : prod80 (Bas Veth)
    License     : MIT, Copyright (c) 2020 prod80


    MIT License

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.

*/

#include "ReShade.fxh"
#include "PD80_00_Noise_Samplers.fxh"

namespace pd80_levels
{

    /*
        Using depth texture to manipulate levels:
        This feature is very dodgy, so hidden by default
        It's added for people specilized in screenshots and able to understand
        that using depth buffer can be odd on something like Levels
        Uncomment ( remove "//" ) the line below to enable this feature
    */
    #ifndef LEVELS_USE_DEPTH
        #define LEVELS_USE_DEPTH    0 //0 = disable, 1 = enable
    #endif


    //// UI ELEMENTS ////////////////////////////////////////////////////////////////
    uniform bool enable_dither <
        ui_label = "Enable Dithering";
        ui_tooltip = "Enable Dithering";
        ui_category = "Global";
        > = true;
    uniform float dither_strength <
        ui_type = "slider";
        ui_label = "Dither Strength";
        ui_tooltip = "Dither Strength";
        ui_category = "Global";
        ui_min = 0.0f;
        ui_max = 10.0f;
        > = 1.0;
    uniform float3 ib <
        ui_type = "color";
        ui_label = "Black IN Level";
        ui_tooltip = "Black IN Level";
        ui_category = "Levels";
        > = float3(0.0, 0.0, 0.0);
    uniform float3 iw <
        ui_type = "color";
        ui_label = "White IN Level";
        ui_tooltip = "White IN Level";
        ui_category = "Levels";
        > = float3(1.0, 1.0, 1.0);
    uniform float3 ob <
        ui_type = "color";
        ui_label = "Black OUT Level";
        ui_tooltip = "Black OUT Level";
        ui_category = "Levels";
        > = float3(0.0, 0.0, 0.0);
    uniform float3 ow <
        ui_type = "color";
        ui_label = "White OUT Level";
        ui_tooltip = "White OUT Level";
        ui_category = "Levels";
        > = float3(1.0, 1.0, 1.0);
    uniform float ig <
        ui_label = "Gamma Adjustment";
        ui_tooltip = "Gamma Adjustment";
        ui_category = "Levels";
        ui_type = "slider";
        ui_min = 0.05;
        ui_max = 10.0;
        > = 1.0;
    #if( LEVELS_USE_DEPTH == 1 )
    uniform bool display_depth <
        ui_label = "Show depth texture.\nThe below adjustments only apply to white areas.\0Make sure you have your depth texture setup correctly.";
        ui_tooltip = "Show depth texture";
        ui_category = "Levels: Depth";
        > = false;
    uniform float depthStart <
        ui_type = "slider";
        ui_label = "Change Depth Start Plane";
        ui_tooltip = "Change Depth Start Plane";
        ui_category = "Levels: Depth";
        ui_min = 0.0f;
        ui_max = 1.0f;
        > = 0.0;
    uniform float depthEnd <
        ui_type = "slider";
        ui_label = "Change Depth End Plane";
        ui_tooltip = "Change Depth End Plane";
        ui_category = "Levels: Depth";
        ui_min = 0.0f;
        ui_max = 1.0f;
        > = 0.1;
    uniform float depthCurve <
        ui_label = "Depth Curve Adjustment";
        ui_tooltip = "Depth Curve Adjustment";
        ui_category = "Levels: Depth";
        ui_type = "slider";
        ui_min = 0.05;
        ui_max = 8.0;
        > = 1.0;
    uniform float3 ibd <
        ui_type = "color";
        ui_label = "Black IN Level Far";
        ui_tooltip = "Black IN Level Far";
        ui_category = "Levels: Far";
        > = float3(0.0, 0.0, 0.0);
    uniform float3 iwd <
        ui_type = "color";
        ui_label = "White IN Level Far";
        ui_tooltip = "White IN Level Far";
        ui_category = "Levels: Far";
        > = float3(1.0, 1.0, 1.0);
    uniform float3 obd <
        ui_type = "color";
        ui_label = "Black OUT Level Far";
        ui_tooltip = "Black OUT Level Far";
        ui_category = "Levels: Far";
        > = float3(0.0, 0.0, 0.0);
    uniform float3 owd <
        ui_type = "color";
        ui_label = "White OUT Level Far";
        ui_tooltip = "White OUT Level Far";
        ui_category = "Levels: Far";
        > = float3(1.0, 1.0, 1.0);
    uniform float igd <
        ui_label = "Gamma Adjustment Far";
        ui_tooltip = "Gamma Adjustment Far";
        ui_category = "Levels: Far";
        ui_type = "slider";
        ui_min = 0.05;
        ui_max = 10.0;
        > = 1.0;
    #endif
    //// TEXTURES ///////////////////////////////////////////////////////////////////
    
    //// SAMPLERS ///////////////////////////////////////////////////////////////////

    //// DEFINES ////////////////////////////////////////////////////////////////////

    //// FUNCTIONS //////////////////////////////////////////////////////////////////
    uniform float2 pingpong < source = "pingpong"; min = 0; max = 128; step = 1; >;

    float3 levels( float3 color, float3 blackin, float3 whitein, float gamma, float3 outblack, float3 outwhite )
    {
        float3 ret       = saturate( color.xyz - blackin.xyz ) / max( whitein.xyz - blackin.xyz, 0.000001f );
        ret.xyz          = pow( ret.xyz, gamma );
        ret.xyz          = ret.xyz * saturate( outwhite.xyz - outblack.xyz ) + outblack.xyz;
        return ret;
    }

    //// PIXEL SHADERS //////////////////////////////////////////////////////////////
    float4 PS_Levels(float4 pos : SV_Position, float2 texcoord : TEXCOORD) : SV_Target
    {
        float4 color     = tex2D( ReShade::BackBuffer, texcoord );
        // Dither
        // Input: sampler, texcoord, variance(int), enable_dither(bool), dither_strength(float), motion(bool), swing(float)
        float4 dnoise      = dither( samplerRGBNoise, texcoord.xy, 2, enable_dither, dither_strength, 1, 0.5f );

        #if( LEVELS_USE_DEPTH == 1 )
        float depth      = ReShade::GetLinearizedDepth( texcoord ).x;
        depth            = smoothstep( depthStart, depthEnd, depth );
        depth            = pow( depth, depthCurve );
        depth            = saturate( depth + dnoise.w );
        #endif

        color.xyz        = saturate( color.xyz + dnoise.w );
        float3 dcolor    = color.xyz;
        color.xyz        = levels( color.xyz,  saturate( ib.xyz + dnoise.xyz ),
                                               saturate( iw.xyz + dnoise.yzx ),
                                               ig, 
                                               saturate( ob.xyz + dnoise.zxy ), 
                                               saturate( ow.xyz + dnoise.wxz ));
        
        #if( LEVELS_USE_DEPTH == 1 )
        dcolor.xyz       = levels( dcolor.xyz, saturate( ibd.xyz + dnoise.xyz ),
                                               saturate( iwd.xyz + dnoise.yzx ),
                                               igd, 
                                               saturate( obd.xyz + dnoise.zxy ), 
                                               saturate( owd.xyz + dnoise.wxz ));
                                               
        color.xyz        = lerp( color.xyz, dcolor.xyz, depth );
        color.xyz        = lerp( color.xyz, depth.xxx, display_depth );
        #endif
        
        return float4( color.xyz, 1.0f );
    }

    //// TECHNIQUES /////////////////////////////////////////////////////////////////
    technique prod80_03_Levels
    {
        pass DoLevels
        {
            VertexShader   = PostProcessVS;
            PixelShader    = PS_Levels;
        }
    }
}